/*
 *  Copyright (C) 2010 Glauco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.swing.components.documents;

import java.awt.Toolkit;
import java.text.DecimalFormatSymbols;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

/**
 *
 * @author Glauco
 */
public class BaliPlainDocument extends PlainDocument{

    /**
     * Verifica se o Documento representa um numero, e nao um texto.
     */
    boolean numeric;

    /**
     * Verifica se o numero pode ser negativo. Usado apenas quando for numeric.
     * @see #numeric
     */
    boolean negatives;

    /**
     * Tamanho maximo de caracteres que o documento aceita
     */
    int length;

    /**
     * Verifica se numero pode ser decimal. Usado apena quando for numeric.
     * @see #numeric
     */
    boolean decimal;

    /**
     * Usado para aceitar apenas caracteres em maiusculo.
     */
    boolean upper;

    /**
     * Caracter separador de milhar
     */
    final static char separadorMilhar = 
            DecimalFormatSymbols.getInstance().getGroupingSeparator();

    /**
     * Caracter separador decimal.
     */
    final static char separadorDecimal =
            DecimalFormatSymbols.getInstance().getDecimalSeparator();

    public BaliPlainDocument(boolean numeric, boolean negatives, int length,
            boolean decimal, boolean upper) {

        super();

        this.numeric = numeric;
        this.negatives = negatives;
        this.length = length;
        this.decimal = decimal;
        this.upper = upper;

    }

    @Override
    public void replace(int offset, int length, String text, AttributeSet attrs)
            throws BadLocationException {

        if (length == 0 && (text == null || text.length() == 0)) {
            return;
        }

	writeLock();
	try {
            if (length > 0) {
                remove(offset, length);
            }
            if (text != null && text.length() > 0) {
                insertString(offset, text, attrs);
            }
        } finally {
            writeUnlock();
        }
    }

    @Override
    public void insertString(int offset, String str, AttributeSet attrs)
                    throws BadLocationException    {

        if (str == null)
            return;

        if (!numericValidation(str)){
            Toolkit.getDefaultToolkit().beep();
            return ;
        }

        if(upper && !numeric)
            str = str.toUpperCase();

        if (length <= 0){
            super.insertString(offset, str, attrs);
            return;
        }

        if(getLength() == length){
            Toolkit.getDefaultToolkit().beep();
            return ;
        }

        if (getLength() + str.length() <= length)
            super.insertString(offset, str, attrs);
        else{
            int l = length - getLength();
            super.insertString(offset, str.substring(0, l), attrs);
            Toolkit.getDefaultToolkit().beep();
        }
    }

    /**
     * Validacao para Documentos numeric
     * @param str String de dados a ser verificado.
     * @return Se o campo numeric for false retorna sempre verdadeiro, case seja
     * um documento numeric eh verificado se os caracters sao validos para
     * formacao de um numero.
     */
    private boolean numericValidation(String str){

        if(!numeric)
            return true;

        // permite apenas digitos
        for (char c : str.toCharArray()) {

            // Se for um digito, passou.
            if(Character.isDigit(c))
                continue;

            // se for um separador de milhar e o documento ter tamanho
            // para usa-lo, passou.
            if (c == separadorMilhar && (length == 0 || length > 4))
                continue;

            // se for separador decimal e o numero for decimal, passou.
            if (c == separadorDecimal && decimal)
                continue;

            // se aceita negativos, reprovado
            if( !negatives)
                return false;

            // se não é digito, separador de milhar ou decimal,
            //e não for sinal de negativo, reprovado
            if(c != '-')
                return false;
        }

        return true;
    }

    /**
     * Cria um documento de texto.
     * @param length Tamanho do documento de texto.
     * @return um novo documento de texto.
     */
    public static BaliPlainDocument createText(int length){
        return new BaliPlainDocument(false, false, length, false, false);
    }

    /**
     * Cria um documento de texto com apenas caracteres em maiusculo.
     * @param length Tamanho do documento de texto.
     * @return um novo documento de texto.
     */
    public static BaliPlainDocument createUpperText(int length){
        return new BaliPlainDocument(false, false, length, false, true);
    }

    /**
     * Cria um novo documento que representa um numero inteiro positivo.
     * @param length Tamanho do documento de texto.
     * @return um novo documento de texto.
     */
    public static BaliPlainDocument createNumber(int length){
        return new BaliPlainDocument(true, false, length, false, false);
    }

    /**
     * Cria um novo documento que representa um numero inteiro ou decimal
     * positivo.
     * @param length Tamanho do documento de texto.
     * @return um novo documento de texto.
     */
    public static BaliPlainDocument createDecimal(int length){
        return new BaliPlainDocument(true, false, length, true, false);
    }

    /**
     * Cria um novo documento que representa um numero inteiro positivo ou
     * negativo.
     * @param length Tamanho do documento de texto.
     * @return um novo documento de texto.
     */
    public static BaliPlainDocument createNegativesNumber(int length){
        return new BaliPlainDocument(true, true, length, false, false);
    }

    /**
     * Cria um novo documento que representa um numero inteiro ou decimal
     * positivo ou negativo.
     * @param length Tamanho do documento de texto.
     * @return um novo documento de texto.
     */
    public static BaliPlainDocument createNegativesDecimal(int length){
        return new BaliPlainDocument(true, true, length, true, false);
    }
}
